#include "CStateRoom.h"

#include "CBattle.h"
#include "CMinionNode.h"
#include "COverlord.h"
#include "CStateGame.h"
#include "CNetworkManager.h"
#include "CChatBox.h"
#include "CGUIGame.h"


CStateRoom::CStateRoom(CGameManager * mgr) : CState(mgr)
{
  //! TODO: See header for this variable TODO
  if ( mgr->getNetworkManager()->IsHost() )
  {
    mgr->setPlayerID(0);

  }
  else
  {
    mgr->setPlayerID(1);

  }

  game = new CStateGame(mgr);
  game->setBattle(new CBattle());

  mgr->getNetworkManager()->setToServer(true);
}

CStateRoom::~CStateRoom()
{
}


void CStateRoom::init()
{
  parent = env->addTab (rect<s32>(0, 0, 800, 600));

  IGUIImage* background = env->addImage( rect<s32>(0,0,SCREEN_W, SCREEN_H), parent, -1, L"L.O.N.");
  background->setImage(driver->getTexture("front2.png"));
  background->setScaleImage(true);

  IGUIImage* background2 = env->addImage( rect<s32>(0,0,800,40), parent, -1, L"L.O.N.");
  background2->setImage(driver->getTexture("baseline.jpg"));
  background2->setScaleImage(true);

  IGUIImage* background3 = env->addImage( rect<s32>(0,560,800,600), parent, -1, L"L.O.N.");
  background3->setImage(driver->getTexture("lowbaseline.jpg"));
  background3->setScaleImage(true);

  env->addButton(rect<s32>(620, 435, 780, 460), parent, BTN_ADD_MINION, L"Add Minion");

  // Only host can start game
  if ( mgr->getNetworkManager()->IsHost() )
  env->addButton(rect<s32>(620, 515, 780, 540), parent, BTN_START_GAME, L"Start Game");

  env->addButton(rect<s32>(620, 555, 780, 580), parent, BTN_POPSTATE, L"Leave Room");

  txtPlayer[0] = env->addStaticText(L"Host", rect<s32>(20, 20, 300, 40), false, true, parent);
  lstPlayer[0] = env->addListBox(rect<s32>(20, 40, 300, 180), parent);
  if ( mgr->getPlayerID() == 0 )
    env->addButton(rect<s32>(20, 180, 120, 200), parent, BTN_REMOVE_MINION, L"Remove", L"Remove selected minion");

  txtPlayer[1] = env->addStaticText(L"Client", rect<s32>(20, 210, 300, 230), false, true, parent);
  lstPlayer[1] = env->addListBox(rect<s32>(20, 230, 300, 370), parent);
  if ( mgr->getPlayerID() == 1 )
    env->addButton(rect<s32>(20, 370, 120, 390), parent, BTN_REMOVE_MINION, L"Remove", L"Remove selected minion");

  // Set up the player minion list
  lstMinion = env->addListBox(rect<s32>(610, 100, 790, 300), parent, 309);
  for (u32 i = 0; i< mgr->getPlayer()->getMinionCount(); i++)
  {
    CMinion *pMinion = mgr->getPlayer()->getMinion(i);

    vListMinion.push_back(pMinion);
    lstMinion->addItem(pMinion->getStats().name.c_str() );
  }

  if ( mgr->getPlayerID() == 0 )
    txtPlayer[0]->setOverrideColor(SColor(255, 255, 0, 0));
  else
    txtPlayer[1]->setOverrideColor(SColor(255, 255, 0, 0));

  mgr->getChatBox()->setVisible(true);
}

void CStateRoom::close()
{
  parent->remove();
  mgr->getChatBox()->setVisible(false);
}

void CStateRoom::run()
{
  driver->beginScene(true, true, SColor(255,0,128,128));
  smgr->drawAll();

  env->drawAll();
}


void CStateRoom::enable()
{
  mgr->getChatBox()->setVisible(true);
  parent->setVisible(true);
}

void CStateRoom::disable()
{
  mgr->getChatBox()->setVisible(false);
  parent->setVisible(false);
}

//! Event Receiving Methods
void CStateRoom::OnClientPacketReceived(Packet *packet)
{
  BitStream *bs = new BitStream(packet->data, packet->length, false);
  switch (packet->data[0])
  {
    case ID_LON_OVERLORD_DATA:
      ackOverlordData(bs);
      break;

    case ID_LON_ADD_MINION:
      ackAddMinion(bs);
      break;

    case ID_LON_REMOVE_MINION:
      ackRemoveMinion(bs);
      break;

    case ID_LON_GAME_START:
      ackStartGame(bs);
      break;

    /// Default RakNet Messages
    case ID_NEW_INCOMING_CONNECTION:
      std::cout << "A player has join the game." << std::endl;
      reqOverlordData();
      break;

    case ID_CONNECTION_REQUEST_ACCEPTED:
      std::cout << "Successfully joined game room." << std::endl;
      break;

    case ID_CONNECTION_ATTEMPT_FAILED:
      std::cout << "Failed to join game room." << std::endl;
      break;

    case ID_NO_FREE_INCOMING_CONNECTIONS:
      std::cout << "The game is full." << std::endl;//->setID(12345);
      break;

    case ID_REMOTE_NEW_INCOMING_CONNECTION:
      std::cout << "Another player has joined the game." << std::endl;
      break;

    case ID_REMOTE_DISCONNECTION_NOTIFICATION:
    case ID_REMOTE_CONNECTION_LOST:
      std::cout << "A player has left the game." << std::endl;
      break;

    case ID_DISCONNECTION_NOTIFICATION:
      if (mgr->getNetworkManager()->IsHost())
      {
        std::cout << "A client has disconnected." << std::endl;
      }
      else
      {
        env->addMessageBox(L"Error",L"You have been disconnected.");
        std::cout << "You have been disconnected." << std::endl;
        //! TODO: Better methods of Force pop state;
        mgr->getNetworkManager()->leaveRoom();
        mgr->popState();
      }
      break;

    case ID_CONNECTION_LOST:
      if (mgr->getNetworkManager()->IsHost()){
        std::cout << "A client lost the connection." << std::endl;
      } else {
        std::cout << "Connection lost." << std::endl;
        //! TODO: Better methods of Force pop state;
        mgr->getNetworkManager()->leaveRoom();
        mgr->popState();
      }
      break;
    default:
      std::cout << "Message with identifier " << int (packet->data[0]) << " has arrived." << std::endl;
      break;
  }

  delete bs;
}

void CStateRoom::OnServerPacketReceived(Packet *packet)
{

}

//! Called when mouse is clicked
bool CStateRoom::OnGUIEvent(const SEvent& event)
{
  s32 id = event.GUIEvent.Caller->getID();

  switch(event.GUIEvent.EventType)
  {
    case EGET_LISTBOX_CHANGED:
    case EGET_LISTBOX_SELECTED_AGAIN:
      if ( id == 309 )
      {
        updateIcon();
        return true;
      }
      break;
    default:
      return false;
      break;
  }

  return false;
}

//! Called when a GUI button
bool CStateRoom::OnButtonClick(s32 id)
{
  //! Temp
  switch (id)
  {
    case BTN_ADD_MINION:
      //! Add Minion
      if ( lstMinion->getSelected() != -1 )
      {
        reqAddMinion();
      }
      return true;
      break;

    case BTN_REMOVE_MINION:
      // Get the selected minion from player list
      if ( lstPlayer[mgr->getPlayerID()]->getSelected() != -1 )
      {
        reqRemoveMinion();
      }
      return true;
      break;

    case BTN_START_GAME:
      //! Start game
      //StartGame(true);

      for ( u32 i=0; i<MAX_PLAYERS; i++ )
      {
        if ( vPlayerMinion[i].size() <= 0 )
        // if ( vPlayerMinion[i].size() != MAX_MINION )
        {
          mgr->getChatBox()->addText(L"Each player must have 3 minions");
          return true;
        }
      }
      reqStartGame();
      return true;
      break;

    case BTN_POPSTATE:
      mgr->getNetworkManager()->leaveRoom();
      mgr->popState();
      return true;
      break;



    default:
      break;
  }

  return false;
}

void CStateRoom::startGame()
{
  //! TODO: Need some refactor on how the states and init and close
  //! See below for more info on this bug
  CMinionNode *temp;
  f32 x, z;

  // Ends this state
  mgr->popState();
  mgr->pushState(game);
  for (u32 i=0; i<MAX_MINION; i++)
  {
    for (u32 j=0; j<MAX_PLAYERS; j++ )
    {
      x = vPositionX.back();
      z = vPositionX.back();

      vPositionX.pop_back();
      vPositionZ.pop_back();

      if ( !vPlayerMinion[j].empty() )
      {
        CMinion *pMinion = vPlayerMinion[j].back();
        vPlayerMinion[j].pop_back();

        temp = game->getBattle()->addMinion(game, pMinion->getStats(), j);
        temp->prepareNode();
        temp->setPosition(x, z);
      }
    }
  }

  //! The first delay update could not be initiated because of this problem
  game->getGUI()->updateDelay(game->getBattle()->getMinionList());
}

void CStateRoom::updateIcon()
{
  /** Old function
  list<minionType*>::Iterator it = vMinionType.begin() + lstMinion->getSelected();
  minionType *selected = (*it);
  minionPic->setImage(selected->icon);
  **/
}

void CStateRoom::reqOverlordData()
{
  BitStream bs;
  bs.Write(u8(ID_LON_OVERLORD_DATA));

  mgr->getPlayer()->netWrite(bs);

  mgr->getNetworkManager()->sendPacket(&bs, false);
}

void CStateRoom::ackOverlordData(BitStream *bs)
{
  u8 msgId;
  bs->Read(msgId);

  if ( mgr->getNetworkManager()->IsHost() )
  {
    // Get data from bitstream
    COverlord *player = new COverlord(SColor(255, 0, 0, 255));
    player->netRead(*bs);

    // Add first player (host, which is self)
    game->getBattle()->addPlayer(mgr->getPlayer());
    mgr->getPlayer()->setID(0);
    mgr->getPlayer()->setTeamColor(SColor(255, 255, 0, 0));

    // Add senocd player
    game->getBattle()->addPlayer(player);
    player->setID(1);

    // Tells the client minions added by the host before client join
    synchronizeClient();
  }

  else
  {
    // Get data from bitstream
    COverlord *host = new COverlord(SColor(255, 255, 0, 0));
    host->netRead(*bs);

    // Add first player (host)
    game->getBattle()->addPlayer(host);
    host->setID(0);

    game->getBattle()->addPlayer(mgr->getPlayer());
    mgr->getPlayer()->setID(1);
    mgr->getPlayer()->setTeamColor(SColor(255, 0, 0, 255));

    reqOverlordData();
  }
}

void CStateRoom::synchronizeClient()
{
  u32 ID = mgr->getPlayerID();
  s32 index = -1; // to cater new ID_LON_ADD_MINION packet format
  for ( u32 i=0; i<vPlayerMinion[ID].size(); i++ )
  {
    BitStream bs;

    bs.Write(u8(ID_LON_ADD_MINION));

    bs.Write(ID);

    bs.Write(index);

    vPlayerMinion[ID].at(i)->getStats().netWrite(bs);

    mgr->getNetworkManager()->sendPacket(&bs, false);
  }
}

void CStateRoom::reqAddMinion()
{
  BitStream bs;
  u32 ID = mgr->getPlayerID();
  s32 index = lstMinion->getSelected();

  bs.Write(u8(ID_LON_ADD_MINION));

  bs.Write(ID);
  bs.Write(index);
  CMinion *pMinion = vListMinion[index];
  pMinion->getStats().netWrite(bs);

  //! TODO: This is a very unsafe method of removing minion. The data might be lost
  //! if the request does not reach the host. A better method is required

  /** Suggested method: Send the player/minion data to the host and then the host
  request the minion data from the server instead of passing the minion data.
  This can prevent intentional fake data transfer from a fake/modificed client as well.
  **/
  if (mgr->getNetworkManager()->IsHost())
  {
    vPlayerMinion[ID].push_back(pMinion);
    lstPlayer[ID]->addItem(pMinion->getStats().name.c_str());
    vListMinion.erase(vListMinion.begin()+index);
    lstMinion->removeItem(index);
  }



  if ( index >= lstMinion->getItemCount() )
    index = s32(lstMinion->getItemCount()) - 1;

  lstMinion->setSelected(index);

  mgr->getNetworkManager()->sendPacket(&bs, false);


}

void CStateRoom::ackAddMinion(BitStream *bs)
{
  u8 msgId;
  u32 ID;
  s32 index;
  minionStats stat;
  bs->Read(msgId);

  bs->Read(ID);
  bs->Read(index);
  stat.netRead(*bs);


  CMinion *pMinion;
  if (mgr->getPlayerID() == ID)
  {
    pMinion = vListMinion[index];
    vListMinion.erase(vListMinion.begin()+index);
    lstMinion->removeItem(index);

    if ( index >= lstMinion->getItemCount() )
      index = s32(lstMinion->getItemCount()) - 1;

    lstMinion->setSelected(index);
  }
  else
  {
    pMinion = new CMinion(stat);
  }

  vPlayerMinion[ID].push_back(pMinion);
  lstPlayer[ID]->addItem(stat.name.c_str());

  // Tell client the data is added
  if (mgr->getNetworkManager()->IsHost())
    mgr->getNetworkManager()->sendPacket(bs, false);

}

void CStateRoom::reqRemoveMinion()
{
  BitStream bs;

  u32 ID = mgr->getPlayerID();
  s32 index = lstPlayer[ID]->getSelected();

  // Write to bitstream
  bs.Write(u8(ID_LON_REMOVE_MINION));
  bs.Write(ID);
  bs.Write(index);

  // Send
  mgr->getNetworkManager()->sendPacket(&bs, false);

  // Host self execute
  if (mgr->getNetworkManager()->IsHost())
  {
    CMinion *pMinion = vPlayerMinion[ID].at(index);

    lstPlayer[ID]->removeItem(index);
    vPlayerMinion[ID].erase(vPlayerMinion[ID].begin()+index);

    vListMinion.push_back(pMinion);
    lstMinion->addItem(pMinion->getStats().name.c_str());

    if ( index >= lstPlayer[ID]->getItemCount() )
      index = s32(lstPlayer[ID]->getItemCount()) - 1;

    lstPlayer[ID]->setSelected(index);
  }
}

void CStateRoom::ackRemoveMinion(BitStream *bs)
{
  u8 msgId;
  u32 ID;
  s32 index;
  s32 lstIndex;

  bs->Read(msgId);
  bs->Read(ID);
  bs->Read(index);

  cout << "Packet ID_LON_REMOVE_MINION received." << endl;
  cout << "Player ID: " << ID << endl;
  cout << "Index: " << index << endl;

  lstIndex = lstPlayer[ID]->getSelected();

  CMinion *pMinion = vPlayerMinion[ID].at(index);

  lstPlayer[ID]->removeItem(index);
  vPlayerMinion[ID].erase(vPlayerMinion[ID].begin()+index);

  // If owner, Put back to the right list:
  if ( mgr->getPlayerID() == ID )
  {
    vListMinion.push_back(pMinion);
    lstMinion->addItem(pMinion->getStats().name.c_str());
  }
  // Else, remove it from memory
  else
  {
    delete pMinion;
  }

  if ( lstIndex >= lstPlayer[ID]->getItemCount() )
      lstIndex = s32(lstPlayer[ID]->getItemCount()) - 1;

    lstPlayer[ID]->setSelected(lstIndex);

  // Tell client the data is added
  if (mgr->getNetworkManager()->IsHost())
    mgr->getNetworkManager()->sendPacket(bs, false);

}


void CStateRoom::reqStartGame()
{
  BitStream bs;
  s32 count;
  bs.Write(u8(ID_LON_GAME_START));

  preparePosition();
  count = vPositionX.size();

  bs.Write(count);

  for (int i=0; i<count; i++ )
  {
    bs.Write(vPositionX[i]);
    bs.Write(vPositionZ[i]);
  }

  mgr->getNetworkManager()->sendPacket(&bs, false);

  startGame();
}


void CStateRoom::ackStartGame(BitStream *bs)
{
  u8 msgId;
  s32 count;
  f32 x, z;
  bs->Read(msgId);

  bs->Read(count);

  for (int i=0; i<count; i++ )
  {
    bs->Read(x);
    bs->Read(z);

    vPositionX.push_back(x);
    vPositionZ.push_back(z);
  }

  startGame();

}

void CStateRoom::preparePosition()
{
  for (u32 i=0; i<MAX_MINION; i++)
  {
    for (u32 j=0; j<MAX_PLAYERS; j++ )
    {
      vPositionX.push_back(rand() % 512);
      vPositionZ.push_back(rand() % 512);
    }
  }
}
